/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.polarrose.wsf.controller;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import com.polarrose.json.JsonSerializer;
import com.polarrose.wsf.WebServiceAccount;
import com.polarrose.wsf.WebServiceAccountManager;
import com.polarrose.wsf.WebServiceError;
import com.polarrose.wsf.action.ActionHandlerException;
import com.polarrose.wsf.action.WebServiceActionContext;
import com.polarrose.wsf.action.WebServiceActionHandler;
import com.polarrose.wsf.annotation.ValidatorParameter;
import com.polarrose.wsf.annotation.ValidatorParameters;
import com.polarrose.wsf.annotation.WebServiceParameter;
import com.polarrose.wsf.stats.CallStatistics;
import com.polarrose.wsf.stats.StatisticsCollector;
import com.polarrose.wsf.validator.Validator;

public class WebServiceController implements Controller, BeanFactoryAware {
	public static final Log log = LogFactory.getLog(WebServiceController.class);

	private StatisticsCollector statsCollector = new StatisticsCollector();

	private Map<String, String> actions = new HashMap<String, String>();

	@Required
	public void setActions(Map<String, String> actions) {
		this.actions = actions;
	}

	private String version;

	@Required
	public void setVersion(String version) {
		this.version = version;
	}

	private WebServiceAccountManager accountManager;

	public void setAccountManager(WebServiceAccountManager accountManager) {
		this.accountManager = accountManager;
	}

	//

	/**
	 * Thread local to store the current request id.
	 */

	private static final ThreadLocal<String> currentRequestId = new ThreadLocal<String>();

	/**
	 * Return the current request id. The request id is a unique string.
	 * 
	 * @return the current request id.
	 */

	public static String getCurrentRequestId() {
		return currentRequestId.get();
	}

	/**
	 * Global JSON serializer used to convert responses to JSON.
	 */

	JsonSerializer jsonSerializer = new JsonSerializer();

	//

	/**
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */

	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		currentRequestId.set(UUID.randomUUID().toString());

		long t0 = System.currentTimeMillis(), t1;
		boolean wasSuccess = false;
		try {
			checkBasicRequestStructure(request);
			WebServiceAccount account = checkAuthorization(request);
			if (handleStatisticsRequest(request, response)) {
				wasSuccess = true;
				return null;
			}
			WebServiceActionHandler actionHandler = instantiateWebServiceActionHandler(request);
			transmogrifyParametersOnActionHandler(request, actionHandler);
			Object actionHandlerExecutionResult = executeActionHandler(actionHandler, new WebServiceActionContext(request, getCurrentRequestId(), account));
			writeSuccessResponse(response, actionHandlerExecutionResult);
			wasSuccess = true;
		} catch (ActionHandlerException e) {
			writeErrorResponse(response, e.getErrors());
		} finally {
			t1 = System.currentTimeMillis();
			statsCollector.incrementCounter(request.getParameter("Action"), wasSuccess, t1 - t0);
		}

		return null;
	}

	private Object executeActionHandler(WebServiceActionHandler actionHandler, WebServiceActionContext webServiceActionContext) throws ActionHandlerException {
		try {
			return actionHandler.execute(webServiceActionContext);
		} catch (ActionHandlerException e) {
			throw e;
		} catch (Throwable t) {
			if (log.isErrorEnabled()) {
				log.error("Cannot execute the ActionHandler", t);
			}
			throw new ActionHandlerException(new WebServiceError("InternalError", "There is an internal problem, which you cannot resolve. Please retry the request."));
		}
	}

	private void transmogrifyParametersOnActionHandler(HttpServletRequest request, WebServiceActionHandler actionHandler) throws ActionHandlerException, IntrospectionException,
			IllegalAccessException, InstantiationException {
		List<WebServiceError> errors = new ArrayList<WebServiceError>();

		BeanInfo beanInfo = Introspector.getBeanInfo(actionHandler.getClass());

		for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
			Method setter = propertyDescriptor.getWriteMethod();
			if (setter != null && setter.isAnnotationPresent(WebServiceParameter.class)) {
				Object parameterValue;

				if (isListType(propertyDescriptor.getPropertyType())) {
					parameterValue = transmogrifyListType(request, propertyDescriptor, actionHandler);
				} else {
					parameterValue = transmogrifySimpleType(request, propertyDescriptor, actionHandler);
				}

				if (parameterValue != null) {
					try {
						propertyDescriptor.getWriteMethod().invoke(actionHandler, parameterValue);
					} catch (Throwable t) {
						if (log.isErrorEnabled()) {
							log.error("Cannot set parameter " + propertyDescriptor.getName() + " on ActionHandler " + actionHandler.getClass().getName(), t);
						}
						throw new ActionHandlerException(new WebServiceError("InternalError", "There is an internal problem, which you cannot resolve. Please retry the request."));
					}
				}
			}
		}

		if (errors.size() != 0) {
			throw new ActionHandlerException(errors);
		}
	}

	private Object transmogrifySimpleType(HttpServletRequest request, PropertyDescriptor propertyDescriptor, WebServiceActionHandler actionHandler) throws ActionHandlerException,
			IllegalAccessException, InstantiationException {
		Object value = null;

		Method setter = propertyDescriptor.getWriteMethod();

		WebServiceParameter webServiceParameter = setter.getAnnotation(WebServiceParameter.class);
		String parameterName = StringUtils.capitalize(propertyDescriptor.getName());

		String parameterValue = request.getParameter(parameterName);
		if (parameterValue != null) {
			// Do initial conversion from string to the expected type

			value = convertParameter(parameterName, parameterValue, propertyDescriptor.getPropertyType(), actionHandler.getClass());

			// Validate

			String validationError;

			if (webServiceParameter.validator() != Validator.class) {
				Class<? extends Validator> validatorClass = webServiceParameter.validator();
				Validator validator = validatorClass.newInstance();
				Annotation[] annotations = setter.getAnnotations();
				for (Annotation annotation : annotations) {
					if (ValidatorParameter.class.isAssignableFrom(annotation.getClass())) {
						applyValidatorParameter(validatorClass, validator, annotation);
					}
					if (ValidatorParameters.class.isAssignableFrom(annotation.getClass())) {
						ValidatorParameters parameters = (ValidatorParameters) annotation;
						ValidatorParameter[] validatorParameters = parameters.parameters();
						for (ValidatorParameter validatorParameter : validatorParameters) {
							applyValidatorParameter(validatorClass, validator, validatorParameter);
						}
					}
				}
				try {
					validationError = validator.validate(value);
				} catch (Throwable t) {
					if (log.isErrorEnabled()) {
						log
								.error("Cannot validate value of parameter " + parameterName + " of ActionHandler " + actionHandler.getClass().getName() + " using validator "
										+ validatorClass.getName(), t);
					}
					throw new ActionHandlerException(new WebServiceError("InternalError", "There is an internal problem, which you cannot resolve. Please retry the request."));
				}
				if (validationError != null) {
					throw new ActionHandlerException(new WebServiceError("InvalidParameterValue", "The value of the '" + parameterName + "' parameter is invalid: " + validationError));
				}
			}
		}

		return value;
	}

	private Object transmogrifyListType(HttpServletRequest request, PropertyDescriptor propertyDescriptor, WebServiceActionHandler actionHandler) throws ActionHandlerException,
			IllegalAccessException, InstantiationException {
		Method setter = propertyDescriptor.getWriteMethod();

		WebServiceParameter webServiceParameter = setter.getAnnotation(WebServiceParameter.class);
		String parameterName = StringUtils.capitalize(propertyDescriptor.getName());

		List<Object> list = new ArrayList<Object>();

		for (int i = 0; request.getParameter(parameterName + "." + i) != null; i++) {
			String parameterValue = request.getParameter(parameterName + "." + i);
			String validationError;

			// Do initial conversion from string to the expected type

			Object convertedValue;
			try {
				Class<?> componentType = getComponentType(propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0]);
				convertedValue = convertParameter(parameterName + "." + i, parameterValue, componentType, actionHandler.getClass());
			} catch (Throwable t) {
				if (log.isErrorEnabled()) {
					log.error("Cannot convert value of parameter " + parameterName + " of ActionHandler " + actionHandler.getClass().getName() + " to type "
							+ propertyDescriptor.getPropertyType().getSimpleName(), t);
				}
				throw new ActionHandlerException(new WebServiceError("InvalidParameterValue", "The value of the '" + parameterName
						+ "' parameter is invalid. Expected a valid TODO String|Integer|Double| TODO"));
			}

			// Validate

			if (webServiceParameter.validator() != Validator.class) {
				Class<? extends Validator> validatorClass = webServiceParameter.validator();
				Validator validator = validatorClass.newInstance();
				Annotation[] annotations = setter.getAnnotations();
				for (Annotation annotation : annotations) {
					if (ValidatorParameter.class.isAssignableFrom(annotation.getClass())) {
						applyValidatorParameter(validatorClass, validator, annotation);
					}
					if (ValidatorParameters.class.isAssignableFrom(annotation.getClass())) {
						ValidatorParameters parameters = (ValidatorParameters) annotation;
						ValidatorParameter[] validatorParameters = parameters.parameters();
						for (ValidatorParameter validatorParameter : validatorParameters) {
							applyValidatorParameter(validatorClass, validator, validatorParameter);
						}
					}
				}
				try {
					validationError = validator.validate(convertedValue);
				} catch (Throwable t) {
					if (log.isErrorEnabled()) {
						log
								.error("Cannot validate value of parameter " + parameterName + " of ActionHandler " + actionHandler.getClass().getName() + " using validator "
										+ validatorClass.getName(), t);
					}
					throw new ActionHandlerException(new WebServiceError("InternalError", "There is an internal problem, which you cannot resolve. Please retry the request."));
				}
				if (validationError != null) {
					throw new ActionHandlerException(new WebServiceError("InvalidParameterValue", "The value of the '" + parameterName + "' parameter is invalid: " + validationError));
				}
			}

			list.add(convertedValue);
		}

		return list;
	}

	private void applyValidatorParameter(Class<? extends Validator> validatorClass, Validator validator, Annotation annotation) throws ActionHandlerException {
		ValidatorParameter validatorParameter = (ValidatorParameter) annotation;
		String validatorProperty = validatorParameter.name();
		String validatorSetter = "set" + StringUtils.capitalize(validatorProperty);
		String validatorSetterValue = validatorParameter.value();
		try {
			Method method = validatorClass.getMethod(validatorSetter, String.class);
			method.invoke(validator, validatorSetterValue);
		} catch (Exception e) {
			log.error("Cannot set validator property " + validatorProperty + " of Validator " + validatorClass.getName() + " to " + validatorSetterValue);
			throw new ActionHandlerException(new WebServiceError("InvalidValidatorConfiguration", "Cannot set validator property " + validatorProperty + " of Validator " + validatorClass.getName()
					+ " to " + validatorSetterValue));
		}
	}

	private WebServiceAccount checkAuthorization(HttpServletRequest request) throws ActionHandlerException {
		// Short circuit if no account manager has been specified

		if (accountManager == null) {
			return null;
		}

		List<WebServiceError> errors = new ArrayList<WebServiceError>();

		// Check if the required auth parameters are there

		String[] requiredParameters = new String[] { "Signature", "SignatureVersion", "Action", "Timestamp", "AccessKey" };

		for (String requiredParameter : requiredParameters) {
			String actionName = request.getParameter(requiredParameter);
			if (actionName == null) {
				errors.add(new WebServiceError("MissingRequiredParameter", "The '" + requiredParameter + "' parameter is missing."));
			}
		}

		if (errors.size() != 0) {
			throw new ActionHandlerException(errors);
		}

		WebServiceAccount account = accountManager.getAccountByAccessKey(request.getParameter("AccessKey"));
		if (account == null) {
			throw new ActionHandlerException(new WebServiceError("AuthFailure", "The AccessKey is unknown or disabled."));
		}

		String signature = createSignature(account, generateSignatureData(request.getParameterMap()));
		if (!signature.equals(request.getParameter("Signature"))) {
			throw new ActionHandlerException(new WebServiceError("AuthFailure", "The signature of the request is not correct."));
		}

		return account;
	}

	private WebServiceActionHandler instantiateWebServiceActionHandler(HttpServletRequest request) throws ActionHandlerException, IntrospectionException {
		// Check if the action exists

		String actionName = request.getParameter("Action");
		if (!actions.containsKey(actionName)) {
			throw new ActionHandlerException(new WebServiceError("UnknownAction", "Action " + actionName + " is unknown to the Web Service."));
		}

		// Check the required parameters for this handler

		List<WebServiceError> errors = new ArrayList<WebServiceError>();

		Class actionClass = beanFactory.getType(actions.get(actionName));
		BeanInfo beanInfo = Introspector.getBeanInfo(actionClass);

		for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
			Method setter = propertyDescriptor.getWriteMethod();
			if (setter != null && setter.isAnnotationPresent(WebServiceParameter.class)) {
				WebServiceParameter webServiceParameter = setter.getAnnotation(WebServiceParameter.class);
				String parameterName = StringUtils.capitalize(propertyDescriptor.getName());
				if (isListType(propertyDescriptor.getPropertyType())) {
					if (!webServiceParameter.optional() && request.getParameter(parameterName + ".0") == null) {
						errors.add(new WebServiceError("MissingRequiredParameter", "The '" + parameterName + "' multiple-value parameter is missing. It should have at least one value specified."));
					}
				} else {
					if (!webServiceParameter.optional() && request.getParameter(parameterName) == null) {
						errors.add(new WebServiceError("MissingRequiredParameter", "The '" + parameterName + "' parameter is missing."));
					}
				}
			}
		}

		if (errors.size() > 0) {
			throw new ActionHandlerException(errors);
		}

		// Instantiate the action

		WebServiceActionHandler actionHandler = (WebServiceActionHandler) beanFactory.getBean(actions.get(actionName), WebServiceActionHandler.class);
		if (actionHandler == null) {
			if (log.isErrorEnabled()) {
				log.error("Cannot instantiate the action handler");
			}
			throw new ActionHandlerException(new WebServiceError("InternalError", "There is an internal problem, which you cannot resolve. Please retry the request."));
		}

		return actionHandler;
	}

	private void checkBasicRequestStructure(HttpServletRequest request) throws ActionHandlerException {
		List<WebServiceError> errors = new ArrayList<WebServiceError>();

		// Check if the version is there if we need one

		if (this.version != null) {
			if (request.getParameter("Version") == null) {
				errors.add(new WebServiceError("MissingRequiredParameter", "The 'Version' parameter is missing."));
			} else if (!this.version.equals(request.getParameter("Version"))) {
				errors.add(new WebServiceError("NoSuchVersion", "An incorrect version was specified in the request. The current version of the API is " + this.version));
			}
		}

		// Check if the request has parameters with multiple values, which is
		// not allowed

		Enumeration parameterNamesEnumeration = request.getParameterNames();
		while (parameterNamesEnumeration.hasMoreElements()) {
			String parameterName = (String) parameterNamesEnumeration.nextElement();
			String[] parameterValues = request.getParameterValues(parameterName);
			if (parameterValues.length > 1) {
				errors.add(new WebServiceError("InvalidParameterValue", "Parameters cannot be specified multiple times."));
			}
		}

		if (errors.size() > 0) {
			throw new ActionHandlerException(errors);
		}
	}

	private boolean isListType(Class<?> propertyType) {
		return List.class.isAssignableFrom(propertyType);
	}

	public static Class getComponentType(Object genericType) {
		Class paramClass = null;

		if (genericType instanceof ParameterizedType) {
			ParameterizedType type = (ParameterizedType) genericType;
			if (type.getActualTypeArguments()[0] instanceof Class) {
				paramClass = (Class) type.getActualTypeArguments()[0];
			} else if (type.getActualTypeArguments()[0] instanceof WildcardType) {
				WildcardType wildcardType = (WildcardType) type.getActualTypeArguments()[0];
				if (wildcardType.getUpperBounds()[0] instanceof Class) {
					paramClass = (Class) wildcardType.getUpperBounds()[0];
				}
			}
		}

		return paramClass;
	}

	private void writeErrorResponse(HttpServletResponse response, List<WebServiceError> errors) throws IOException {
		WebServiceErrorResponse errorResponse = new WebServiceErrorResponse(getCurrentRequestId(), errors);
		response.setContentType("text/javascript");
		PrintWriter writer = response.getWriter();
		writer.println(jsonSerializer.serialize(errorResponse));
		writer.flush();
	}

	private void writeSuccessResponse(HttpServletResponse response, Object result) throws IOException {
		WebServiceSuccessResponse successResponse = new WebServiceSuccessResponse(getCurrentRequestId(), result);
		response.setContentType("text/javascript");
		PrintWriter writer = response.getWriter();
		writer.println(jsonSerializer.serialize(successResponse));
		writer.flush();
	}

	private boolean handleStatisticsRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
		if ("GetCallStatistics".equals(request.getParameter("Action"))) {
			Map<String, Map<String, Object>> responseObject = new HashMap<String, Map<String, Object>>();
			CallStatistics[] stats = statsCollector.getAllCallStatistics();
			for (CallStatistics callStatistics : stats) {
				Map<String, Object> values = new HashMap<String, Object>();
				values.put("averageResponseTime", callStatistics.getAverageResponseTime());
				values.put("totalInvocations", callStatistics.getTotalInvocations());
				values.put("totalFailures", callStatistics.getTotalFailures());
				responseObject.put(callStatistics.getCallName(), values);
			}
			writeSuccessResponse(response, responseObject);
			return true;
		}
		return false;
	}

	/**
	 * The bean factory from which this controller is coming. Needed to
	 * instantiate ActionHandlers.
	 */

	private BeanFactory beanFactory;

	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	//

	private String generateSignatureData(Map parameters) {
		// Create a sorted list of the query parameter names

		List<String> sortedNames = new ArrayList<String>();
		for (Object name : parameters.keySet()) {
			if (!name.equals("Signature")) {
				sortedNames.add(name.toString());
			}
		}

		Collections.sort(sortedNames, String.CASE_INSENSITIVE_ORDER);

		// Concatenate the sorted name/value pairs to form the signature data

		StringBuffer signatureData = new StringBuffer();
		for (String name : sortedNames) {
			signatureData.append(name);
			String[] values = (String[]) parameters.get(name);
			signatureData.append(values[0]);
		}

		return signatureData.toString();
	}

	private final static String HMAC_SHA1 = "HmacSHA1";

	private String createSignature(WebServiceAccount account, String data) {
		try {
			// Create a Mac instance and initialize it with the AWS secret key
			SecretKeySpec secretKeySpec = new SecretKeySpec(account.getSecretKey().getBytes(), HMAC_SHA1);
			Mac mac = Mac.getInstance(HMAC_SHA1);
			mac.init(secretKeySpec);
			// Compute the HMAC and return the base64 encoded result
			byte[] rawSignature = mac.doFinal(data.getBytes());
			return new String(Base64.encodeBase64(rawSignature));
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("Cannot get an instance of Mac " + HMAC_SHA1);
		} catch (InvalidKeyException e) {
			throw new RuntimeException("Invalid secret key: " + account.getSecretKey());
		}
	}

	//

	private static final Set<Class<?>> simpleTypes = new HashSet<Class<?>>();

	static {
		simpleTypes.add(String.class);
		simpleTypes.add(Boolean.class);
		simpleTypes.add(Byte.class);
		simpleTypes.add(Character.class);
		simpleTypes.add(Short.class);
		simpleTypes.add(Integer.class);
		simpleTypes.add(Long.class);
		simpleTypes.add(Float.class);
		simpleTypes.add(Double.class);
		simpleTypes.add(BigDecimal.class);
		simpleTypes.add(URL.class);
	}

	private static final Map<Class<?>, Class<?>> autoboxTypes = new HashMap<Class<?>, Class<?>>();

	static {
		autoboxTypes.put(int.class, Integer.class);
		autoboxTypes.put(boolean.class, Boolean.class);
		autoboxTypes.put(byte.class, Byte.class);
		autoboxTypes.put(short.class, Short.class);
		autoboxTypes.put(byte.class, Byte.class);
		autoboxTypes.put(long.class, Long.class);
		autoboxTypes.put(float.class, Float.class);
		autoboxTypes.put(double.class, Double.class);
	}

	private Object convertParameter(String name, String value, Class<?> clazz, Class<? extends WebServiceActionHandler> actionHandlerClass) throws ActionHandlerException {
		try {
			if (autoboxTypes.containsKey(clazz)) {
				clazz = autoboxTypes.get(clazz);
			}

			if (simpleTypes.contains(clazz)) {
				try {
					Constructor c = clazz.getConstructor(String.class);
					return c.newInstance(value);
				} catch (Throwable t) {
					if (log.isErrorEnabled()) {
						log.error("Cannot create a new " + clazz.getSimpleName() + " instance: " + t.getMessage(), t);
					}
					throw new RuntimeException("Cannot create a new " + clazz.getSimpleName() + " instance: " + t.getMessage());
				}
			} else {
				throw new RuntimeException("Don't know how to convert to a parameter of type " + clazz.getSimpleName());
			}
		} catch (Throwable t) {
			if (log.isErrorEnabled()) {
				log.error("Cannot convert value of parameter " + name + " of ActionHandler " + actionHandlerClass.getName() + " to type " + clazz.getSimpleName(), t);
			}
			throw new ActionHandlerException(new WebServiceError("InvalidParameterValue", "The value of the '" + name + "' parameter is invalid. Expected a valid primitive type"));
		}
	}

	//

	public static abstract class WebServiceResponse {
		private final String webServiceCallId;

		public String getWebServiceCallId() {
			return webServiceCallId;
		}

		protected WebServiceResponse(String webServiceCallId) {
			this.webServiceCallId = webServiceCallId;
		}
	}

	public static class WebServiceSuccessResponse extends WebServiceResponse {
		private final Object response;

		public Object getResponse() {
			return response;
		}

		public WebServiceSuccessResponse(String webServiceCallId) {
			super(webServiceCallId);
			this.response = null;
		}

		public WebServiceSuccessResponse(String webServiceCallId, Object response) {
			super(webServiceCallId);
			this.response = response;
		}
	}

	public static class WebServiceErrorResponse extends WebServiceResponse {
		private List<WebServiceError> errors;

		public List<WebServiceError> getErrors() {
			return errors;
		}

		public WebServiceErrorResponse(String webServiceCallId, List<WebServiceError> errors) {
			super(webServiceCallId);
			this.errors = errors;
		}
	}
}
